package agents;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.ParallelBehaviour;
import jade.domain.FIPAAgentManagement.Property;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import model.Artifact;
import model.ContentDTO;
import model.IdentificationProtocol;
import model.Profile;
import model.TourObject;
import behaviours.RegisterServiceBehaviour;
import behaviours.SubscribeAndListenBehaviour;
import behaviours.Subscriber;

public class TourGuideAgent extends Agent implements Subscriber {

	private ArrayList<AID> curators;
	private static final int MAX_ARTIFACTS_IN_TOUR = 2;

	protected void setup() {
		System.out.println(getAID() + " running setup.");

		curators = new ArrayList<AID>();

		// Subscribe to curators
		String[] providers = { "artifact-provider" };
		addBehaviour(new SubscribeAndListenBehaviour(this, providers));

		// Register with DF
		ServiceDescription sd = new ServiceDescription();
		sd.setType("tour-creation");
		sd.setName("touragent");
		sd.addProperties( new Property("Profile", null));
		ServiceDescription[] sds = new ServiceDescription[1];
		sds[0] = sd;
		
		addBehaviour(new RegisterServiceBehaviour(getAID(), sds));

		// Start listening to tour requests
		addBehaviour(new ListenToTourRequestsBehaviour());
	}

	protected void takeDown() {
		System.out.println("takeDown()");
	}

	@Override
	public void updateSubscription(String type, AID name) {
		if (type.equals("artifact-provider")) {
			curators.add(name);
			System.out
					.println("TourGuideAgent added " + name + " to curators.");
		}
	}

	private class ListenToTourRequestsBehaviour extends CyclicBehaviour {

		@Override
		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.REQUEST));
			if (msg != null) {
				try {
					// This contains the profile from the profiler agent
					Object obj = msg.getContentObject();
					ContentDTO content = null;
					if (obj instanceof ContentDTO)
						content = (ContentDTO) obj;
					if (content != null) {
						Profile prof = (Profile) content.getContent();

						// Request potentially interesting artifacts from
						// curator
						myAgent.addBehaviour(new RequestArtifactsAndListenBehaviour(
								prof, msg.getSender()));
					}
				} catch (UnreadableException e) {
					e.printStackTrace();
				}
			} else
				block();
		}
	}

	private class RequestArtifactsAndListenBehaviour extends ParallelBehaviour {

		public RequestArtifactsAndListenBehaviour(Profile prof, AID sender) {

			// Sends a request to curator with the given interests
			addSubBehaviour(new SendRequestToCuratorBehaviour(
					prof.getInterestTags()));

			// Listens for a reply and puts it in the messageStore
			addSubBehaviour(new ListenToCuratorBehaviour(prof, sender));
		}
	}

	private class SendRequestToCuratorBehaviour extends OneShotBehaviour {

		private ArrayList<String> interestTags;

		public SendRequestToCuratorBehaviour(ArrayList<String> interestTags) {
			this.interestTags = interestTags;
		}

		@Override
		public void action() {
			// Send request to curator with this profile
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
			for (AID a : curators)
				msg.addReceiver(a);
			try {
				msg.setContentObject(new ContentDTO(interestTags,
						IdentificationProtocol.SENDER_WAS_TOUR_GUIDE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			send(msg);
		}
	}

	private class ListenToCuratorBehaviour extends Behaviour {

		private AID sender;
		private Profile profile;
		private boolean msgReceived;

		public ListenToCuratorBehaviour(Profile prof, AID sender) {
			this.sender = sender;
			this.profile = prof;
			msgReceived = false;
		}

		@Override
		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.CONFIRM));
			if (msg != null) {
				try {
					msgReceived = true;
					Object obj = msg.getContentObject();
					ContentDTO receivedObject = null;

					if (obj instanceof ContentDTO)
						receivedObject = (ContentDTO) obj;

					if (receivedObject.getContent() instanceof ArrayList<?>) {
						@SuppressWarnings("unchecked")
						ArrayList<Artifact> artifacts = (ArrayList<Artifact>) receivedObject
								.getContent();
						myAgent.addBehaviour(new GenerateTourAndReplyBehaviour(
								sender, artifacts, this.profile));
					}

				} catch (UnreadableException e) {
					e.printStackTrace();
				}

			} else
				block();
		}

		@Override
		public boolean done() {
			return msgReceived;
		}
	}

	private class GenerateTourAndReplyBehaviour extends OneShotBehaviour {

		private AID sender;
		private ArrayList<Artifact> suggestedArtifacts;
		private Profile profile;

		public GenerateTourAndReplyBehaviour(AID sender,
				ArrayList<Artifact> art, Profile profile) {
			this.suggestedArtifacts = art;
			this.sender = sender;
			this.profile = profile;
		}

		@Override
		public void action() {			
			// Generate tour
			ArrayList<TourObject> tour = generateTour();

			// Reply to tour agent
			ACLMessage msg = new ACLMessage(ACLMessage.CONFIRM);
			msg.addReceiver(sender);
			try {
				msg.setContentObject(new ContentDTO(tour,
						IdentificationProtocol.SENDER_WAS_TOUR_GUIDE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			send(msg);
		}

		private ArrayList<TourObject> generateTour() {

			ArrayList<TourObject> tour = new ArrayList<TourObject>();

			tour = sortArtifacts(profile, suggestedArtifacts);

			return tour;
		}

		private ArrayList<TourObject> sortArtifacts(Profile prof,
				ArrayList<Artifact> artifacts) {
			ArrayList<String> interestTags = prof.getInterestTags();

			ArrayList<TourObject> relevantObjects = new ArrayList<TourObject>();

			HashMap<Artifact, Integer> scoreBoard = new HashMap<Artifact, Integer>();
			for (Artifact art : artifacts) {
				scoreBoard.put(art, 0);
			}

			// For every interest tag received
			for (String tag : interestTags) {
				// Check every artifact
				for (Artifact art : artifacts) {
					// Find the current score for the artifact
					int currentScore = scoreBoard.get(art);

					// Check name
					if (art.getName().equals(tag))
						currentScore++;

					// Check for every genre
					for (String genre : art.getGenres()) {
						if (genre.equals(tag))
							currentScore++;
					}

					// Check creator
					if (art.getCreator().equals(tag))
						currentScore++;

					try {
						// Check year of creation
						if (art.getYearOfCreation() == Integer.parseInt(tag))
							currentScore++;
					} catch (NumberFormatException e) {
						// If this happens, the tag was not a year of creation
						// and we don't do anything about it
					}

					// Check place of creation
					if (art.getPlaceOfCreation().equals(tag))
						currentScore++;

					// Update the scoreboard
					scoreBoard.put(art, currentScore);
				}
			}

			ArrayList<Artifact> sortedArtifacts = sortArtifactsAfterScore(
					scoreBoard, artifacts);

			// Make sure we remove any artifacts already visited by the user

			for (Artifact art : sortedArtifacts) {
				if (relevantObjects.size() < MAX_ARTIFACTS_IN_TOUR) {
					if (notVisited(art, prof)) {
						TourObject to = new TourObject(art.getId(),
								art.getName());
						relevantObjects.add(to);
					}
				}
			}

			return relevantObjects;
		}

		private boolean notVisited(Artifact art, Profile prof) {			
			return !prof.getVisitedArtifacts().containsKey(art.getId());
		}

		private ArrayList<Artifact> sortArtifactsAfterScore(
				HashMap<Artifact, Integer> scoreBoard,
				ArrayList<Artifact> artifacts) {

			ArrayList<Artifact> sortedArtifacts = new ArrayList<Artifact>();
			ArrayList<Artifact> unsortedArtifacts = new ArrayList<Artifact>(
					artifacts);

			// As long as the scoreboard has artifacts left
			while (!unsortedArtifacts.isEmpty()) {
				Artifact highestRated = null;

				// Find the highest valued artifact, remove it, find the next
				// highest value artifact, remove it
				// Check every artifact
				for (Artifact art : unsortedArtifacts) {

					if (highestRated != null) {
						// If the current unsorted artifact has a higher score
						// than the currently highest rated
						if (scoreBoard.get(highestRated) < scoreBoard.get(art))
							highestRated = art;
					} else {
						highestRated = art;
					}
				}

				sortedArtifacts.add(highestRated);
				unsortedArtifacts.remove(highestRated);
			}

			return sortedArtifacts;
		}
	}
}
